#include "structMeshBase.hpp"
#include "meshTypes.hpp"
#include "structMeshFunctions.hpp"
#include "macros.hpp"

namespace hms {

#define REQUIRE_MEMBER_FUNC(FUNC_NAME) \
	static_assert(std::is_member_function_pointer_v< \
		decltype(&MeshType::FUNC_NAME) \
	>);

template<typename MeshType>
StructMeshBase<MeshType>::StructMeshBase(){
	REQUIRE_MEMBER_FUNC(nCellsXImp)
	REQUIRE_MEMBER_FUNC(nCellsYImp)
	REQUIRE_MEMBER_FUNC(sizeImp)
}


#define FORWARD_FUNC_CALL_NOARG(NAME,RET) \
	template<typename MeshType> \
	auto StructMeshBase<MeshType>::NAME() \
	const -> RET { \
		return this->derived().NAME##Imp(); \
	}

FORWARD_FUNC_CALL_NOARG(nCellsX,Index)
FORWARD_FUNC_CALL_NOARG(nCellsY,Index)
FORWARD_FUNC_CALL_NOARG(size,SizeType)

template<typename MeshType>
auto StructMeshBase<MeshType>::cell(Index x, Index y) const -> CellType {
	return { this->derived(), x, y };
}

template<typename MeshType>
auto StructMeshBase<MeshType>::cellImp(Index i) const -> CellType {
	return { this->derived(), i };
}

template<typename MeshType>
auto StructMeshBase<MeshType>::edgeImp(Index i) const -> EdgeType {
	return { this->derived(), i };
}



template<typename MeshType>
auto StructMeshBase<MeshType>::nVerticesImp() const -> Index {
	return (nCellsX()+1)*(nCellsY()+1);
}

template<typename MeshType>
auto StructMeshBase<MeshType>::nEdgesImp() const -> Index {
	return this->nInnerEdges() + this->nBoundaryEdges();
}
template<typename MeshType>
auto StructMeshBase<MeshType>::nInnerEdgesImp   () const -> Index {
	return
		2 * nCellsX() * nCellsY()
		- ( nCellsX() + nCellsY() );
}
template<typename MeshType>
auto StructMeshBase<MeshType>::nBoundaryEdgesImp() const -> Index {
	return
		2 * ( nCellsX() + nCellsY() );
}

template<typename MeshType>
auto StructMeshBase<MeshType>::nCellsImp        () const -> Index {
	return this->nDomainCells() + this->nGhostCells();
}
template<typename MeshType>
auto StructMeshBase<MeshType>::nDomainCellsImp  () const -> Index {
	return nCellsX()*nCellsY();
}
template<typename MeshType>
auto StructMeshBase<MeshType>::nGhostCellsImp   () const -> Index {
	return 2 * ( nCellsX()+nCellsY() );
}

template<typename MeshType>
auto StructMeshBase<MeshType>::moveBoundaryPatches() ->
	std::vector<BoundaryPatch>&&
{
	return std::move( this->m_boundaryPatches );
}



template<typename MeshType>
auto StructMeshBase<MeshType>::blocksize() const -> Index { return m_blocksize ;}

template<typename MeshType>
void StructMeshBase<MeshType>::blocksize( Index val ) { m_blocksize = val; }


template<typename MeshType>
void StructMeshBase<MeshType>::checkBoundaryPatch( const ArrayXi& edges) const {
	bool problem {false};
	switch ( whereIs(edges(0), Array2i{this->nCellsX(), this->nCellsY()}) ){
		case BoundaryLocation::bottom: [[fallthrough]];
		case BoundaryLocation::top:
			if ( ( edges != ArrayXi::LinSpaced(
				edges.size(), edges(0), edges(0) + edges.size() - 1) ).any()
			){ problem = true; }
			break;
		case BoundaryLocation::left: [[fallthrough]];
		case BoundaryLocation::right:
			if ( ( edges != ArrayXi::LinSpaced( edges.size(),
				edges(0), edges(0) + (edges.size()-1)*(this->nCellsX()+1)
				) ).any()                /* the stride ^^^^^^^^^^^^^^^^^ */
			){ problem = true; }
			break;
		default:
			UNREACHABLE;
	}
	if (problem)
		throw MeshError{
			"Boundary patch of structured mesh must be continuous!"
		};
}


#undef REQUIRE_MEMBER_FUNC
#undef FORWARD_FUNC_CALL_NOARG

template class StructMeshBase<UniMesh>;
template class StructMeshBase<RectMesh>;
template class StructMeshBase<StructMesh>;

}